Passed
Push — master ( c1d503...0431b4 )
by Andrii
02:39
created

index.ts ➔ writer   A

Complexity

Conditions 3

Size

Total Lines 30
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
eloc 22
dl 0
loc 30
rs 9.352
c 0
b 0
f 0
cc 3
1
import {resolve} from "path"
2
import { regexpize, extractDefaults, readlineSync, $unlink } from './utils'
3
import schema = require("./schema.json")
4
import type { Options } from './options.types'
5
import replaceMultiplicated = require('./replaceMultiplicated')
6
import collector = require('./collector')
7
import rewrite = require('./rewrite')
8
import type { InternalOptions, WithSource } from './$defs.types'
9
10
type Opts = Required<Options>
11
12
const {keys: $keys} = Object
13
, defaultOptions = extractDefaults(schema) as Opts
14
, {
15
  title,
16
  signature,
17
  templateEol,
18
  properties: {template: {$comment: templatePath}}
19
} = schema
20
, defaultTemplate = readlineSync(resolve(__dirname, templatePath), templateEol)
21
22
const creator8 = (opts?: Options) => {
23
  const options = makeOpts(opts)
24
25
  return {
26
    postcssPlugin: title,
27
    prepare: (result: {
28
      warn: (arg: string) => any
29
      root: WithSource
30
    }) => {
31
      //TODO #12 template update check
32
33
      /* istanbul ignore next `source === undefined` for manually created node with `.decl` */
34
      if (!result.root?.source?.input.file)
35
        return {}
36
37
      try {
38
        optsCheck(options)
39
      } catch ({message}) {
40
        // TODO throw error
41
        result.warn(message)
42
        return {}
43
      }
44
45
      // https://jsbench.me/q5km8xdgbb
46
      const identifiers: Record<string, true> = {}
47
48
      return {
49
        RuleExit: collector(identifiers, options),
50
        RootExit: writer(identifiers, options)
51
      }
52
    }
53
  } //as Plugin
54
}
55
56
creator8.postcss = true
57
58
export = creator8
59
60
function optsCheck({
61
  destination,
62
  identifierParser
63
}: {destination: any} & Pick<InternalOptions, "identifierParser">) {
64
  if (!(destination === false || destination !== null && typeof destination === "object"))
65
    throw Error("Destination is of wrong type")
66
67
  //TODO check sticky
68
  if (!identifierParser.flags.includes('g'))
69
    throw Error('identifierParser should have global flag')
70
}
71
72
function makeOpts(opts?: Options) {
73
  const options = !opts ? defaultOptions : {...defaultOptions, ...opts}
74
  , {
75
    eol,
76
    destination,
77
    //TODO several keywords?
78
    identifierKeyword,
79
    identifierMatchIndex,
80
    identifierCleanupReplace,
81
  } = options
82
83
  return {
84
    eol,
85
    destination,
86
    identifierKeyword,
87
    identifierMatchIndex,
88
    identifierCleanupReplace,
89
    ...internalOpts(options)
90
  }
91
}
92
93
function internalOpts({
94
  eol,
95
  template: templatePath,
96
  identifierPattern: cssP,
97
  identifierCleanupPattern: escapedP,
98
  allowedAtRules: atRules,
99
  checkMode
100
}: Pick<Opts, "eol"|"template"|"identifierPattern"|"identifierCleanupPattern"|"allowedAtRules"|"checkMode">): InternalOptions {
101
  const identifierParser = regexpize(cssP, "g")
102
  , identifierCleanupParser = regexpize(escapedP, "g")
103
  //TODO check `templatePath === ""`
104
  , templateContent = typeof templatePath === "string"
105
  // TODO not sync
106
  ? readlineSync(templatePath, eol)
107
  : defaultTemplate
108
109
  , allowedAtRuleNames = new Set(atRules)
110
111
  return {
112
    identifierParser,
113
    identifierCleanupParser,
114
    templateContent,
115
    allowedAtRuleNames,
116
    checkMode: checkMode ?? process.env.NODE_ENV === "production"
117
  }
118
}
119
120
function writer(
121
  identifiers: Record<string, any>,
122
  {
123
    eol,
124
    templateContent,
125
    identifierKeyword,
126
    destination,
127
    checkMode
128
  }: Pick<Opts, "eol"|"identifierKeyword"|"destination">
129
  & Pick<InternalOptions, "templateContent"|"checkMode">
130
) {
131
  return (async ({source}: WithSource) => {
132
    //TODO ? Change `sort` with option
133
    const keys = $keys(identifiers).sort()
134
    , file = source!.input.file!
135
    , target = `${file}.d.ts`
136
    , lines = replaceMultiplicated(
137
      signature.concat(templateContent),
138
      identifierKeyword,
139
      keys
140
    )
141
142
    if (destination === false) {
143
      if (keys.length !== 0)
144
        return await rewrite(target, lines, eol, checkMode)
145
146
      await $unlink(target)
147
    } else
148
      destination[file] = lines
149
  })
150
}
151